/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "intromit.hh"
#include "dwarf.hh"
#include "metadata.hh"
#include "util.hh"

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <getopt.h>

#define ZSTR        "\000"

static struct option long_options[]=
{
    {"help"           ZSTR"Show this help and exit",       no_argument,       NULL, 'h'},
    {"version"        ZSTR"Show version and exit",         no_argument,       NULL, 'V'},
    {"output"         ZSTR"Product output name",           required_argument, NULL, 'o'},
    {"verbose"        ZSTR"Verbosity level (default is 1).\n"
                          "Could be used multiple times",  optional_argument, NULL, 'v'},
    {"config-file"    ZSTR"Config file instead of default",required_argument, NULL, 'c'},
    {"config_file"    ZSTR"",                              required_argument, NULL, 'c'},
    {"modules-path"   ZSTR"List of paths separated by ':' to\n"
                          "search for generator modules",  required_argument, NULL, 'm'},
    {"modules_path"   ZSTR"",                              required_argument, NULL, 'm'},
    {"unmng_metadata" ZSTR"Dump unmanageable metadata to "
                          "specified file.\n"
                          "If verbose mode >1 (i.e. "
                          "-v2 or -vv), stdout is used",   required_argument, NULL, 'u'},
    {"unmng-metadata" ZSTR"",                              required_argument, NULL, 'u'},
    {"unresolubled"   ZSTR"Dump unresolubled symbols of "
                          "ELF files.\n"
                          "If verbose mode >1 (i.e. "
                          "-v2 or -vv), stdout is used",   required_argument, NULL, 'U'},
    {"dbg_config_file"ZSTR"debug config file dumping "
                          "post-parsed result to file",    required_argument, NULL, 0x100},
    {"dbg-config-file"ZSTR"",                              required_argument, NULL, 0x100},
    {"dbg_dwarf_bases"ZSTR"debug dwarf bases dumping "
                          "intermediate bases to /tmp",    no_argument,       NULL, 0x101},
    {"dbg-dwarf-bases"ZSTR"",                              no_argument,       NULL, 0x101},
    {"dbg_pattern"    ZSTR"debug patterns for metadata "
                          "matching -> file",              required_argument, NULL, 0x102},
    {"dbg-pattern"    ZSTR"",                              required_argument, NULL, 0x102},
    {"dbg_symbols"    ZSTR"debug ELF symbols -> file",     required_argument, NULL, 0x103},
    {"dbg-symbols"    ZSTR"",                              required_argument, NULL, 0x103},
    {"dbg_off_attrs"  ZSTR"debug offline attrs -> file",   required_argument, NULL, 0x104},
    {"dbg-off-attrs"  ZSTR"",                              required_argument, NULL, 0x104},
    {"dbg_orig_bases" ZSTR"dump ELF orig, bases -> file",  required_argument, NULL, 0x105},
    {"dbg-orig-bases" ZSTR"",                              required_argument, NULL, 0x105},
    {0, 0, 0, 0}
};

void usage()
{
    printf("Usage: %s [options] file... \nOptions:\n", PACKAGE);

    // options
    for(int i=0; long_options[i].name; i++)
    {
	const char *name=long_options[i].name;
	const char *help=&name[strlen(name)+1];
	if(*help)
	{
	    if(long_options[i].val<0x100) printf("  -%c, --%-16s ", long_options[i].val, name);
	    else printf("      --%-16s ", name);
	    char *new_line;
	    do
	    {
		char help_buffer[256];
		if((new_line=strchr(const_cast<char *>(help), '\n')))
		{
		    int size=new_line-help;
		    if(size>255) size=255;
		    strncpy(help_buffer, help, size);
		    help_buffer[size]=0;
		    help=help_buffer;
		    new_line++;
		}
		printf("%s\n", help);
		if(new_line)
		{
		    help=new_line;
		    printf("%*s", 2+6+16+1, "");
		}
	    } while(new_line);
	}
    }
}

int main(int argc, char *argv[])
{
    // command line customization
    int finish=0, error=0, showed_help=0, showed_version=0;
    command_options opts;
    memset(&opts, 0, sizeof(command_options));
    opts.verbo=1;

    // original bases
    char *orig_bases=NULL;

    // parse command line
    opterr=0; // make getopt to be quiet
    while(1)
    {
	int c, option_index=-1;
	int i_option=optind?optind:1;
	const char *optstring="hVo:c:v::u:U:m:";
	if((c=getopt_long(argc, argv, optstring, long_options, &option_index))==-1) break;
	switch(c)
	{
	case 0x100: // dbg_config_file
	    opts.dbg_config_file=optarg;
	    break;

	case 0x101: // dbg_dwarf_bases
	    opts.dbg_dwarf_bases++;
	    break;

	case 0x102: // dbg_pattern
	    opts.dbg_pattern=optarg;
	    break;

	case 0x103: // dbg_symbols
	    opts.dbg_symbols=optarg;
	    break;

	case 0x104: // dbg_off_attrs
	    opts.dbg_off_attrs=optarg;
	    break;

	case 0x105: // dbg_orig_bases
	    orig_bases=optarg;
	    break;

	case 'U':
	    opts.unresolubled=optarg;
	    break;

	case 'u':
	    opts.unmanageable=optarg;
	    break;

	case 'h':
	    if(!showed_help++) usage();
	    finish++;
	    break;

	case 'V':
	    if(!showed_version++) printf("%s %s\n", PACKAGE, VERSION);
	    finish++;
	    break;

	case 'o':
	    opts.output=optarg;
	    break;

	case 'c':
	    opts.config_file=optarg;
	    break;

	case 'm':
	    opts.modules_path=optarg;
	    break;

	case 'v':
	    opts.verbo++;
	    if(optarg)
	    {
		while(*optarg=='v') {opts.verbo++; optarg++;}
		if(*optarg) opts.verbo=atoi(optarg);
	    }
	    break;

	case '?':
	    if(optopt>=0x100) MSG("argument to '%s' is missing", argv[i_option]);
	    else if(optopt && strchr(optstring, optopt)) MSG("argument to '-%c' is missing", optopt);
	    else if(!optopt) MSG("unrecognized option '%s'", argv[i_option]);
	    else MSG("unrecognized option '-%c'", optopt);
	    error++;
	    break;

	default:
	    MSG("unmanaged getopt option '-%c'", c);
	    error++;
	    break;
	}
    }

    // error implies exit
    if(error) return 1;

    // specific command resulted in premature exit
    if(finish) return 0;

    // rest of non-option parameters
    if(optind<argc)
    {
	opts.elf_list=&argv[optind];
	opts.n_elf_list=argc-optind;

	// check that there is not a single output file for multiple input
	if(opts.n_elf_list>1 && opts.output)
	    MSG_R1("multiple input files, but single output specified");
    }
    else
    {
	MSG("no input ELF files supplied");
	return 2;
    }

    // original bases of first elf
    if(orig_bases)
    {
	dwarf dw;
	dw.dump(opts.elf_list[0], orig_bases);
    }

    // just do it
    metadata md;
    if(!md.generate(&opts)) return 3; else return 0;
}
